Remove all [Obsolete] members and migrate callers#5384
Conversation
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers
Overall this is a clean, well-scoped removal PR that follows through on prior deprecation work. The migration from HasCount/HasLength to Count().IsEqualTo(N) / Length().IsEqualTo(N) is executed consistently across the entire codebase, and removing ObjectBag / Timing / RecordTiming completes an earlier breaking-change cycle properly.
A few observations worth flagging:
1. StringLengthWithInlineAssertionAssertion duplicates CollectionCountWithInlineAssertionAssertion almost exactly
The new class in TUnit.Assertions/Conditions/StringAssertions.cs is structurally identical to CollectionCountWithInlineAssertionAssertion<TCollection, TItem> in CollectionCountSource.cs. Both:
- Accept a
Func<IAssertionSource<int>, Assertion<int>?>lambda - Create a
ValueAssertion<int>internally - Catch failures and return a generic "was N" failure message
- Use
"to satisfy X assertion"as their expectation string
This duplication means any future improvement (better failure messages, async evaluation, etc.) would have to be applied in two places. A shared base class or a helper method (e.g. InlineNumericAssertionHelper.Run(...)) would eliminate the duplication. The CollectionCountWithInlineAssertionAssertion pattern already exists and works — StringLengthWithInlineAssertionAssertion could simply extend or delegate to the same mechanism.
2. The new Length(lambda) overload silently swallows the inner assertion's failure message
When the lambda assertion fails, both the existing Count(lambda) and the new Length(lambda) implementations catch the exception and return a generic AssertionResult.Failed($"length was {_actualLength}"). This loses the inner assertion's error message entirely.
For example:
await Assert.That(str).Length(l => l.IsGreaterThan(10));
// Failure: "expected to satisfy length assertion but length was 3"
// vs. what the user wants: "expected length to be greater than 10 but received 3"The CollectionCountWithInlineAssertionAssertion has the same issue — this PR is a good opportunity to fix it in both places by propagating the inner exception message rather than discarding it.
3. TimingEntry is now internal record but structurally identical to the deleted public Timing record
The deleted Timing record was:
public record Timing(string StepName, DateTimeOffset Start, DateTimeOffset End)
{
public TimeSpan Duration => End - Start;
}The new TimingEntry in TestContext.Output.cs is:
internal record TimingEntry(string StepName, DateTimeOffset Start, DateTimeOffset End)
{
public TimeSpan Duration => End - Start;
}This is intentional (it's now engine-internal) and correctly placed, but the TestExtensions.cs usage of testContext.Timings still iterates TimingEntry objects to populate TimingProperty for TRX reporting. The fact that TimingEntry lives in TUnit.Core but is only used by TUnit.Engine is fine, but worth verifying that no consumers in TUnit.Engine reference the deleted public Timing type by name (e.g. via ITestOutput.Timings which is now removed). The #pragma disable CS0618 suppressions being removed is a good sign that this is clean.
4. AssertionResult.Failed("value was null") vs. AssertionResult.Failed("collection was null") inconsistency in StringLengthWithInlineAssertionAssertion
The new class returns "value was null" for a null string, while the equivalent count assertion returns "collection was null". This is a minor wording inconsistency — either "string was null" or mirroring "value was null" across both would be more consistent.
Summary
The bulk of the diff (migrating test callsites) is correct and complete. The structural concern is the near-duplicate implementation of StringLengthWithInlineAssertionAssertion — it would be worth extracting the shared lambda-assertion execution pattern into a helper to avoid future drift. The silent failure message swallowing is a pre-existing issue in Count(lambda) that this PR inadvertently carries forward into Length(lambda) as well.
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| BestPractice | 1 minor |
🟢 Metrics 6 complexity
Metric Results Complexity 6
TIP This summary will be updated as you push new changes. Give us feedback
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers (follow-up)
Comparing against the previous review:
Addressed from prior review ✅
Duplication of inline assertion logic: The extraction of InlineAssertionHelper.ExecuteInlineAssertionAsync() correctly eliminates the near-duplicate between CollectionCountWithInlineAssertionAssertion and StringLengthWithInlineAssertionAssertion. The helper now also propagates ex.Message rather than a generic fallback string, which was the other open concern.
Remaining issues
1. LengthWrapper and CountWrapper are now dead code and should be deleted
HasLength() (no-arg overload) and HasCount() (no-arg overload) that returned these wrappers have been removed. There is no longer any way to construct a LengthWrapper or CountWrapper, yet both files remain with only doc-comment updates applied.
These classes should be deleted outright. Keeping them adds noise to the public API surface (they're public) and the dead code signals an incomplete cleanup. Their presence also means the residual "IsTypeOf is not supported after HasCount()" error messages in CountWrapper are permanently stale — further evidence they should go.
2. StringLengthWithInlineAssertionAssertion.GetExpectation() is too vague
This produces failure messages like: "expected to satisfy length assertion but value was null". The equivalent CollectionCountWithInlineAssertionAssertion likely has the same issue. A better approach would be to lazily capture the expectation from the inner assertion after it has been evaluated — the inner Assertion<int> already has its own GetExpectation() which would produce something like "to be equal to 5". Forwarding that gives: "expected length to be equal to 5 but received 3", which is much clearer.
3. InlineAssertionHelper is typed to int only
The helper signature is:
This works today since both count and length are int, but it rules out reuse for long, double, etc. without a second overload. Making it generic (ExecuteInlineAssertionAsync<T>) would cost nothing now and prevent a copy-paste if another numeric type is needed later.
Minor
StringLengthWithInlineAssertionAssertion.CheckAsyncreturnsAssertionResult.Failed("value was null")for a null string, while the count equivalent uses"collection was null"."string was null"would be more specific and consistent with the count wording.
Overall
The bulk of this PR — migrating 80+ callsites and removing the public obsolete API surface — is well-executed and complete. The main actionable items are deleting the now-unreachable CountWrapper/LengthWrapper classes and improving the GetExpectation() message quality in StringLengthWithInlineAssertionAssertion.
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers (follow-up)
Comparing against the previous review:
Addressed from prior review ✅
Duplication of inline assertion logic: The extraction of InlineAssertionHelper.ExecuteInlineAssertionAsync() correctly eliminates the near-duplicate between CollectionCountWithInlineAssertionAssertion and StringLengthWithInlineAssertionAssertion. The helper now also propagates ex.Message rather than a generic fallback string, which was the other open concern.
Remaining issues
1. LengthWrapper and CountWrapper are now dead code and should be deleted
HasLength() (no-arg overload) and HasCount() (no-arg overload) that returned these wrappers have been removed. There is no longer any way to construct a LengthWrapper or CountWrapper, yet both files remain with only doc-comment updates applied.
These classes should be deleted outright. Keeping them adds noise to the public API surface (they are public) and the dead code signals an incomplete cleanup. Their presence also means the residual "IsTypeOf is not supported after HasCount()" error messages in CountWrapper are permanently stale — further evidence they should go.
2. StringLengthWithInlineAssertionAssertion.GetExpectation() is too vague
protected override string GetExpectation()
{
return "to satisfy length assertion";
}
This produces failure messages like: "expected to satisfy length assertion but value was null". A better approach would be to lazily capture the expectation from the inner assertion after evaluation — the inner Assertion<int> already has its own GetExpectation() which produces something like "to be equal to 5". Forwarding that gives: "expected length to be equal to 5 but received 3", which is much clearer. The CollectionCountWithInlineAssertionAssertion likely has the same gap.
3. InlineAssertionHelper is typed to int only
The helper is hard-coded to int. This works today since both count and length fit, but making it generic (ExecuteInlineAssertionAsync<T>) costs nothing now and avoids copy-paste if a long or double variant is needed later.
Minor
StringLengthWithInlineAssertionAssertion.CheckAsyncreturnsAssertionResult.Failed("value was null")for a null string, while the count equivalent uses"collection was null"."string was null"would be more specific and consistent with the count wording.
Overall
The bulk of this PR — migrating 80+ callsites and removing the public obsolete API surface — is well-executed and complete. The main actionable items are deleting the now-unreachable CountWrapper/LengthWrapper classes and improving the GetExpectation() message in StringLengthWithInlineAssertionAssertion.
be058ed to
96c7a43
Compare
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers — follow-up (3rd review)
Comparing against the previous two reviews.
What was addressed ✅
InlineAssertionHelper extraction + inner message propagation — confirmed present in commit 3cf4a1d. The shared helper deduplicates the count/length lambda-assertion execution and correctly propagates ex.Message from the inner assertion rather than replacing it with a generic fallback. This is the right design.
Third commit (96c7a43) test fixes — correctly updates tests to use Length(l => ...) where string context must be preserved for .And chaining, vs Length().IsEqualTo(N) where chaining ends. The comment in the commit message clearly explains the distinction.
Still outstanding from previous reviews
1. LengthWrapper and CountWrapper are unreachable dead code and should be deleted
Both HasLength() (no-arg, returning LengthWrapper) and HasCount() (no-arg, returning CountWrapper) have been removed. There is no longer any way to construct either class — yet both remain as public types with only their doc-comments updated.
Why this matters: They pollute the public API surface, and the NotSupportedException messages inside them (e.g. "IsTypeOf is not supported after HasCount()") are now permanently stale since HasCount() no longer exists. Leaving dead public types in an assembly that just did a deliberate API cleanup is at best confusing to users and future maintainers.
Recommendation: Delete both files.
2. StringLengthWithInlineAssertionAssertion.GetExpectation() returns a vague message
This produces failure messages like: "expected to satisfy length assertion but value was null", which tells the user nothing about what was asserted. The inner Assertion<int> has its own GetExpectation() that would produce something like "to be equal to 5".
A better approach: run the lambda against a no-op source to extract the expectation lazily, or store a reference to the inner assertion and delegate to innerAssertion.GetExpectation() after evaluation. Compare with CollectionCountWithInlineAssertionAssertion — it likely has the same gap and both could be fixed together.
3. InlineAssertionHelper is hardcoded to int
This works today since both count and length are int. Making it generic costs nothing and keeps the helper reusable if a long, double, or TimeSpan inline assertion is ever needed:
Minor (unchanged from previous review)
StringLengthWithInlineAssertionAssertion.CheckAsyncreturnsAssertionResult.Failed("value was null")for a null string. The count equivalent uses"collection was null"."string was null"would be more consistent with that wording pattern.
Overall
The removal work is thorough and correct — 80+ callsites migrated consistently. The three items above are the remaining actionable items, with deleting CountWrapper/LengthWrapper being the most important since those are now live dead code in the public API.
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers — follow-up (3rd review)
Comparing against the previous two reviews.
What was addressed ✅
InlineAssertionHelper extraction + inner message propagation — confirmed present in commit 3cf4a1d. The shared helper deduplicates the count/length lambda-assertion execution and correctly propagates ex.Message from the inner assertion rather than replacing it with a generic fallback. This is the right design.
Third commit (96c7a43) test fixes — correctly updates tests to use Length(l => ...) where string context must be preserved for .And chaining, vs Length().IsEqualTo(N) where chaining ends. The comment in the commit message clearly explains the distinction.
Still outstanding from previous reviews
1. LengthWrapper and CountWrapper are unreachable dead code and should be deleted
Both HasLength() (no-arg, returning LengthWrapper) and HasCount() (no-arg, returning CountWrapper) have been removed. There is no longer any way to construct either class — yet both remain as public types with only their doc-comments updated.
Why this matters: They pollute the public API surface, and the NotSupportedException messages inside them (e.g. "IsTypeOf is not supported after HasCount()") are now permanently stale since HasCount() no longer exists. Leaving dead public types in an assembly that just did a deliberate API cleanup is at best confusing to users and future maintainers.
Recommendation: Delete both files.
2. StringLengthWithInlineAssertionAssertion.GetExpectation() returns a vague message
protected override string GetExpectation()
{
return "to satisfy length assertion";
}
This produces failure messages like: "expected to satisfy length assertion but value was null", which tells the user nothing about what was asserted. The inner Assertion<int> has its own GetExpectation() that would produce something like "to be equal to 5".
A better approach: store a reference to the inner assertion after the lambda is evaluated, and delegate GetExpectation() to innerAssertion.GetExpectation(). This would produce messages like "expected length to be equal to 5 but received 3". The CollectionCountWithInlineAssertionAssertion likely has the same gap and both could be fixed together.
3. InlineAssertionHelper is hardcoded to int
The current signature is:
internal static async Task<AssertionResult> ExecuteInlineAssertionAsync(
int actualValue,
string label,
Func<IAssertionSource<int>, Assertion<int>?> assertion)
This works today since both count and length are int. Making it generic costs nothing now and keeps the helper reusable if a long, double, or TimeSpan inline assertion is ever needed.
Minor (unchanged from previous review)
StringLengthWithInlineAssertionAssertion.CheckAsyncreturnsAssertionResult.Failed("value was null")for a null string, while the count equivalent uses"collection was null"."string was null"would be more specific and consistent with that wording.
Overall
The removal work is thorough and correct — 80+ callsites migrated consistently. The three items above are the remaining actionable items, with deleting CountWrapper/LengthWrapper being the most important since those are now live dead code in the public API.
There was a problem hiding this comment.
PR Review: Remove all [Obsolete] members and migrate callers — follow-up (4th review)
Reviewing the two new commits since the last review: the merge commit and the "Regenerate public API snapshots after removing obsolete members" commit (df093be).
What the new commit does
The regenerated public API snapshots correctly reflect:
HasCount()andHasLength()factory methods removed from all three target frameworks (net8.0, net9.0, net10.0)HasCount(int)andHasLength(int)removedObjectBag,Timing,ITestOutput.Timings,RecordTimingremoved fromTUnit.CoreStringLengthWithInlineAssertionAssertionadded as a new public type
The snapshot regeneration is correct and necessary — good.
Still unresolved: CountWrapper and LengthWrapper remain as public dead code
The regenerated snapshots confirm what was flagged in the previous three reviews. Both classes still appear in the public API surface:
public class CountWrapper<TCollection, TItem> : ., .<TCollection>
public CountWrapper(.<TCollection> context) { }
public class LengthWrapper : ., .<string>
public LengthWrapper(.<string> context) { }
The only way to construct either of these was via HasCount() and HasLength(), which are now deleted. There is no constructor call path left — they are unreachable. Despite this, they remain public types visible to all consumers of TUnit.Assertions.
Why this matters: A future consumer browsing the assembly or IntelliSense will discover CountWrapper and LengthWrapper and have no way to use them. The NotSupportedException messages inside them reference HasCount() / HasLength(), which no longer exist, so the error guidance is permanently incorrect. Leaving them in the public API surface undermines the goal of this PR, which is specifically to clean up the public API.
Recommendation: Delete TUnit.Assertions/Conditions/Wrappers/CountWrapper.cs and TUnit.Assertions/Conditions/Wrappers/LengthWrapper.cs, then re-run snapshot tests to confirm they drop from the .verified.txt files.
Still unresolved: GetExpectation() in StringLengthWithInlineAssertionAssertion is vague
protected override string GetExpectation()
{
return "to satisfy length assertion";
}This produces failure messages like: "expected to satisfy length assertion but value was null". The inner Assertion<int> that the lambda returns has its own GetExpectation(). Delegating to it — after evaluating the lambda — would produce messages like "expected length to be equal to 5 but received 3", which is far more actionable for the test author. The CollectionCountWithInlineAssertionAssertion has the same gap. Both could be fixed together.
Still unresolved: InlineAssertionHelper hardcoded to int
internal static async Task<AssertionResult> ExecuteInlineAssertionAsync(
int actualValue,
string label,
Func<IAssertionSource<int>, Assertion<int>?> assertion)Making this generic (ExecuteInlineAssertionAsync<T>) costs nothing now and avoids duplication if a long, double, or TimeSpan inline assertion is ever needed. The current signature would require either a second copy of this method or a lossy int cast.
Overall
The obsolete API removal and snapshot regeneration are correct and complete. The three remaining items — dead CountWrapper/LengthWrapper classes, vague GetExpectation(), and the int-hardcoded helper — have carried through all four review iterations. The CountWrapper/LengthWrapper deletion is the most important because it directly impacts the exported public API surface that this PR set out to clean up.
There was a problem hiding this comment.
Follow-up Review
All four concerns from the prior review have been addressed. Here's a summary of what was fixed and one remaining observation.
✅ Previous Issues — All Resolved
1. Duplication of inline-assertion execution logic — Fixed via the new InlineAssertionHelper.ExecuteInlineAssertionAsync<T>(). Both CollectionCountWithInlineAssertionAssertion and StringLengthWithInlineAssertionAssertion now delegate to a single shared helper. This is exactly the right abstraction.
2. Inner failure message was silently swallowed — Fixed. Both assertions now store _innerAssertion and use _innerAssertion.InternalGetExpectation() in GetExpectation(), so failure messages reflect the actual constraint (e.g. "count to be equal to 5") rather than a generic fallback.
3. TimingEntry placement and #pragma suppressions — Clean. The internal record lives in TestContext.Output.cs, all obsolete CS0618 suppression pragmas are removed, and the TRX reporting path still works via the internal type.
4. "value was null" wording — Fixed to "string was null", consistent with the equivalent "collection was null" in the count assertion.
Minor Observation — InlineAssertionHelper propagates ex.Message as the failure detail
When resultingAssertion.AssertAsync() throws a TUnitAssertionException, ex.Message is the full formatted failure string (e.g. "Expected count to be equal to 5 but received 3"). That message then becomes the "received" portion of the outer assertion, while GetExpectation() returns "count to be equal to 5". The final failure message may read something like:
Expected count to be equal to 5 but count was: Expected count to be equal to 5 but received 3
Using just the raw value instead (e.g. AssertionResult.Failed($"{label} was {actualValue}")) would be cleaner — though this was already the pre-existing behaviour of Count(lambda), so it's not a regression introduced here. Worth a follow-up issue if it causes confusing output in practice.
Minor Observation — Inconsistency in test callsites for Length()
In OrAssertionTests.cs, Or_With_String_Assertions uses the new lambda overload:
While And_With_String_Assertions and Tests.cs use the existing chain form:
Both are valid, but the test suite would read more consistently if all migrated callsites used the same form. Not a blocking concern.
Overall
This is a well-executed cleanup PR. The breaking changes are appropriate (all removed members carried [Obsolete] warnings), the migration is complete and consistent, the abstraction improvement (InlineAssertionHelper) is the right call, and the public API surface snapshots are correctly updated. Good to go.
There was a problem hiding this comment.
Follow-up Review
All four concerns from the prior review have been addressed. Here's a summary of what was fixed and one remaining observation.
All Previous Issues Resolved
1. Duplication of inline-assertion execution logic — Fixed via the new InlineAssertionHelper.ExecuteInlineAssertionAsync<T>(). Both CollectionCountWithInlineAssertionAssertion and StringLengthWithInlineAssertionAssertion now delegate to a single shared helper. This is the right abstraction.
2. Inner failure message was silently swallowed — Fixed. Both assertions now store _innerAssertion and use _innerAssertion.InternalGetExpectation() in GetExpectation(), so failure messages reflect the actual constraint (e.g. "count to be equal to 5") rather than a generic fallback.
3. TimingEntry placement and #pragma suppressions — Clean. The internal record lives in TestContext.Output.cs, all obsolete CS0618 suppression pragmas are removed, and the TRX reporting path still works via the internal type.
4. "value was null" wording — Fixed to "string was null", consistent with the equivalent "collection was null" in the count assertion.
Minor: InlineAssertionHelper propagates ex.Message as the failure detail
When resultingAssertion.AssertAsync() throws a TUnitAssertionException, ex.Message is the full formatted failure string (e.g. "Expected count to be equal to 5 but received 3"). That message then becomes the "received" portion of the outer assertion, while GetExpectation() returns "count to be equal to 5". The final failure message could read redundantly.
Using just the raw value (e.g. AssertionResult.Failed($"{label} was {actualValue}")) would be cleaner — though this was already the pre-existing behaviour of Count(lambda), so it's not a regression. Worth a follow-up issue if it causes confusing output in practice.
Minor: Inconsistency in test callsites for Length()
In OrAssertionTests.cs, Or_With_String_Assertions uses the new lambda overload (.Length(l => l.IsEqualTo(11))), while And_With_String_Assertions and Tests.cs use the chained form (.Length().IsEqualTo(11)). Both are valid, but the test suite would read more consistently if all migrated callsites used the same form.
Overall
This is a well-executed cleanup PR. The breaking changes are appropriate (all removed members carried [Obsolete] warnings), the migration is complete and consistent across ~60 callsites, the InlineAssertionHelper abstraction is the right call, and the public API snapshots are correctly updated. Good to go.
- Remove ObjectBag properties from TestRegisteredContext and TestBuilderContext (use StateBag) - Remove Timing record and ITestOutput.Timings/RecordTiming (replaced by OpenTelemetry spans); internal TimingEntry record preserves engine functionality - Remove obsolete HasCount()/HasCount(int) from CollectionAssertionBase; migrate all callers to Count().IsEqualTo(N) or Count(c => c.IsEqualTo(N)) - Remove obsolete HasLength()/HasLength(int) from AssertionExtensions; migrate all callers to Length().IsEqualTo(N) or Length(l => l.IsEqualTo(N)) - Add Length(lambda) overload for inline assertions that preserve string context for .And chaining, matching the existing Count(lambda) pattern - Update docs, code examples, and XUnit migration code fixer
…ssages Deduplicate the lambda-assertion execution logic from StringLengthWithInlineAssertionAssertion and CollectionCountWithInlineAssertionAssertion into a shared InlineAssertionHelper. Inner assertion failure messages are now propagated instead of being replaced with a generic "count/length was N".
Length() maps context to int, so And/Or chaining after it cannot return to string assertions. Use Length(l => l.IsEqualTo(N)) which preserves the string context for continued chaining.
…sages, make helper generic - Delete CountWrapper and LengthWrapper (dead code after removing HasCount/HasLength no-arg overloads) - Make InlineAssertionHelper generic and return inner assertion for expectation delegation - Improve GetExpectation() in StringLengthWithInlineAssertionAssertion and CollectionCountWithInlineAssertionAssertion to delegate to inner assertion (e.g. "length to be equal to 5" instead of "to satisfy length assertion") - Fix null message from "value was null" to "string was null" for string length assertions - Remove unused Wrappers namespace imports - Regenerate public API snapshots
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.23.7 to 1.28.7. <details> <summary>Release notes</summary> _Sourced from [TUnit.Core's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.28.7 <!-- Release notes generated using configuration in .github/release.yml at v1.28.7 --> ## What's Changed ### Other Changes * fix: prevent StringBuilder race in console interceptor during parallel tests by @thomhurst in thomhurst/TUnit#5414 ### Dependencies * chore(deps): update tunit to 1.28.5 by @thomhurst in thomhurst/TUnit#5415 **Full Changelog**: thomhurst/TUnit@v1.28.5...v1.28.7 ## 1.28.5 <!-- Release notes generated using configuration in .github/release.yml at v1.28.5 --> ## What's Changed ### Other Changes * perf: eliminate redundant builds in CI pipeline by @thomhurst in thomhurst/TUnit#5405 * perf: eliminate store.ToArray() allocation on mock behavior execution hot path by @thomhurst in thomhurst/TUnit#5409 * fix: omit non-class/struct constraints on explicit interface mock implementations by @thomhurst in thomhurst/TUnit#5413 ### Dependencies * chore(deps): update tunit to 1.28.0 by @thomhurst in thomhurst/TUnit#5406 **Full Changelog**: thomhurst/TUnit@v1.28.0...v1.28.5 ## 1.28.0 <!-- Release notes generated using configuration in .github/release.yml at v1.28.0 --> ## What's Changed ### Other Changes * fix: resolve build warnings in solution by @thomhurst in thomhurst/TUnit#5386 * Perf: Optimize MockEngine hot paths (~30-42% faster) by @thomhurst in thomhurst/TUnit#5391 * Move Playwright install into pipeline module by @thomhurst in thomhurst/TUnit#5390 * perf: optimize solution build performance by @thomhurst in thomhurst/TUnit#5393 * perf: defer per-class JIT via lazy test registration + parallel resolution by @thomhurst in thomhurst/TUnit#5395 * Perf: Generate typed HandleCall<T1,...> overloads to eliminate argument boxing by @thomhurst in thomhurst/TUnit#5399 * perf: filter generated attributes to TUnit-related types only by @thomhurst in thomhurst/TUnit#5402 * fix: generate valid mock class names for generic interfaces with non-built-in type args by @thomhurst in thomhurst/TUnit#5404 ### Dependencies * chore(deps): update tunit to 1.27.0 by @thomhurst in thomhurst/TUnit#5392 * chore(deps): update dependency path-to-regexp to v8 by @thomhurst in thomhurst/TUnit#5378 **Full Changelog**: thomhurst/TUnit@v1.27.0...v1.28.0 ## 1.27.0 <!-- Release notes generated using configuration in .github/release.yml at v1.27.0 --> ## What's Changed ### Other Changes * Fix Dependabot security vulnerabilities in docs site by @thomhurst in thomhurst/TUnit#5372 * fix: use 0.0.0-scrubbed sentinel version in snapshot scrubber to avoid false Dependabot alerts by @thomhurst in thomhurst/TUnit#5374 * Speed up Engine.Tests by removing ProcessorCount parallelism cap by @thomhurst in thomhurst/TUnit#5379 * ci: add concurrency groups to cancel redundant workflow runs by @thomhurst in thomhurst/TUnit#5373 * Add scope-aware initialization and disposal OpenTelemetry spans to trace timeline and HTML report by @Copilot in thomhurst/TUnit#5339 * Add WithInnerExceptions() for fluent AggregateException assertion chaining by @thomhurst in thomhurst/TUnit#5380 * Drop net6.0 and net7.0 TFMs, keep net8.0+ and netstandard2.x by @thomhurst in thomhurst/TUnit#5387 * Remove all [Obsolete] members and migrate callers by @thomhurst in thomhurst/TUnit#5384 * Add AssertionResult.Failed overload that accepts an Exception by @thomhurst in thomhurst/TUnit#5388 ### Dependencies * chore(deps): update dependency mockolate to 2.3.0 by @thomhurst in thomhurst/TUnit#5370 * chore(deps): update tunit to 1.25.0 by @thomhurst in thomhurst/TUnit#5371 * chore(deps): update dependency minimatch to v9.0.9 by @thomhurst in thomhurst/TUnit#5375 * chore(deps): update dependency path-to-regexp to v0.2.5 by @thomhurst in thomhurst/TUnit#5376 * chore(deps): update dependency minimatch to v10 by @thomhurst in thomhurst/TUnit#5377 * chore(deps): update dependency picomatch to v4 by @thomhurst in thomhurst/TUnit#5382 * chore(deps): update dependency svgo to v4 by @thomhurst in thomhurst/TUnit#5383 * chore(deps): update dependency path-to-regexp to v1 [security] by @thomhurst in thomhurst/TUnit#5385 **Full Changelog**: thomhurst/TUnit@v1.25.0...v1.27.0 ## 1.25.0 <!-- Release notes generated using configuration in .github/release.yml at v1.25.0 --> ## What's Changed ### Other Changes * Fix missing `default` constraint on explicit interface implementations with unconstrained generics by @thomhurst in thomhurst/TUnit#5363 * feat(mocks): add ReturnsAsync typed factory overload with method parameters by @thomhurst in thomhurst/TUnit#5367 * Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types by @thomhurst in thomhurst/TUnit#5366 * refactor(mocks): use file-scoped types for generated implementation details by @thomhurst in thomhurst/TUnit#5369 * Compress HTML report JSON data and minify CSS by @thomhurst in thomhurst/TUnit#5368 ### Dependencies * chore(deps): update tunit to 1.24.31 by @thomhurst in thomhurst/TUnit#5356 * chore(deps): update dependency mockolate to 2.2.0 by @thomhurst in thomhurst/TUnit#5357 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5365 * chore(deps): update dependency polyfill to 9.24.1 by @thomhurst in thomhurst/TUnit#5364 **Full Changelog**: thomhurst/TUnit@v1.24.31...v1.25.0 ## 1.24.31 <!-- Release notes generated using configuration in .github/release.yml at v1.24.31 --> ## What's Changed ### Other Changes * Fix Aspire 13.2.0+ timeout caused by ProjectRebuilderResource being awaited by @Copilot in thomhurst/TUnit#5335 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5349 * Fix nullable IParsable type recognition in source generator and analyzer by @Copilot in thomhurst/TUnit#5354 * fix: resolve race condition in HookExecutionOrderTests by @thomhurst in thomhurst/TUnit#5355 * Fix MaxExternalSpansPerTest cap bypass when Activity.Parent chain is broken by @Copilot in thomhurst/TUnit#5352 ### Dependencies * chore(deps): update tunit to 1.24.18 by @thomhurst in thomhurst/TUnit#5340 * chore(deps): update dependency stackexchange.redis to 2.12.14 by @thomhurst in thomhurst/TUnit#5343 * chore(deps): update verify to 31.15.0 by @thomhurst in thomhurst/TUnit#5346 * chore(deps): update dependency polyfill to 9.24.0 by @thomhurst in thomhurst/TUnit#5348 **Full Changelog**: thomhurst/TUnit@v1.24.18...v1.24.31 ## 1.24.18 <!-- Release notes generated using configuration in .github/release.yml at v1.24.18 --> ## What's Changed ### Other Changes * feat(mocks): shorter, more readable generated mock type names by @thomhurst in thomhurst/TUnit#5334 * Fix DisposeAsync() ordering for nested property injection by @Copilot in thomhurst/TUnit#5337 ### Dependencies * chore(deps): update tunit to 1.24.13 by @thomhurst in thomhurst/TUnit#5331 **Full Changelog**: thomhurst/TUnit@v1.24.13...v1.24.18 ## 1.24.13 <!-- Release notes generated using configuration in .github/release.yml at v1.24.13 --> ## What's Changed ### Other Changes * perf(mocks): optimize MockEngine for lower allocation and faster verification by @thomhurst in thomhurst/TUnit#5319 * Remove defunct `UseTestingPlatformProtocol` reference for vscode by @erwinkramer in thomhurst/TUnit#5328 * perf(aspnetcore): prevent thread pool starvation during parallel WebApplicationTest server init by @thomhurst in thomhurst/TUnit#5329 * fix TUnit0073 for when type from from another assembly by @SimonCropp in thomhurst/TUnit#5322 * Fix implicit conversion operators bypassed in property injection casts by @Copilot in thomhurst/TUnit#5317 * fix(mocks): skip non-virtual 'new' methods when discovering mockable members by @thomhurst in thomhurst/TUnit#5330 * feat(mocks): IFoo.Mock() discovery with generic fallback and ORP resolution by @thomhurst in thomhurst/TUnit#5327 ### Dependencies * chore(deps): update tunit to 1.24.0 by @thomhurst in thomhurst/TUnit#5315 * chore(deps): update aspire to 13.2.1 by @thomhurst in thomhurst/TUnit#5323 * chore(deps): update verify to 31.14.0 by @thomhurst in thomhurst/TUnit#5325 ## New Contributors * @erwinkramer made their first contribution in thomhurst/TUnit#5328 **Full Changelog**: thomhurst/TUnit@v1.24.0...v1.24.13 ## 1.24.0 <!-- Release notes generated using configuration in .github/release.yml at v1.24.0 --> ## What's Changed ### Other Changes * perf: optimize TUnit.Mocks hot paths by @thomhurst in thomhurst/TUnit#5304 * fix: resolve System.Memory version conflict on .NET Framework (net462) by @thomhurst in thomhurst/TUnit#5303 * fix: resolve CS0460/CS0122/CS0115 when mocking concrete classes from external assemblies by @thomhurst in thomhurst/TUnit#5310 * feat(mocks): parameterless Returns() and ReturnsAsync() for async methods by @thomhurst in thomhurst/TUnit#5309 * Fix typo in NUnit manual migration guide by @aa-ko in thomhurst/TUnit#5312 * refactor(mocks): unify Mock.Of<T>() and Mock.OfPartial<T>() into single API by @thomhurst in thomhurst/TUnit#5311 * refactor(mocks): clean up Mock API surface by @thomhurst in thomhurst/TUnit#5314 * refactor(mocks): remove generic/untyped overloads from public API by @thomhurst in thomhurst/TUnit#5313 ### Dependencies * chore(deps): update tunit to 1.23.7 by @thomhurst in thomhurst/TUnit#5305 * chore(deps): update dependency mockolate to 2.1.1 by @thomhurst in thomhurst/TUnit#5307 ## New Contributors * @aa-ko made their first contribution in thomhurst/TUnit#5312 **Full Changelog**: thomhurst/TUnit@v1.23.7...v1.24.0 Commits viewable in [compare view](thomhurst/TUnit@v1.23.7...v1.28.7). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
ObjectBagproperties fromTestRegisteredContextandTestBuilderContext(replaced byStateBag)Timingrecord andITestOutput.Timings/RecordTiming(replaced by OpenTelemetry activity spans); internalTimingEntrypreserves engine functionalityHasCount()/HasCount(int)fromCollectionAssertionBase; all callers migrated toCount().IsEqualTo(N)orCount(c => c.IsEqualTo(N))HasLength()/HasLength(int)fromAssertionExtensions; all callers migrated toLength().IsEqualTo(N)orLength(l => l.IsEqualTo(N))Length(lambda)overload (StringLengthWithInlineAssertionAssertion) for inline assertions that preserve string context for.Andchaining, matching the existingCount(lambda)patternTest plan
TUnit.Assertions.TestsTUnit.Assertions.SourceGenerator.Tests,TUnit.Core.SourceGenerator.TestsTUnit.Mocks.TestsTUnit.IntegrationTests.verified.txtfiles for public API changes